﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;

namespace Codemonk.Common.Emit
{
    internal static class ExpressionHelper
    {
        private static Type OBJECT_TYPE = typeof(object);
        private static Type OBJECTARRAY_TYPE = typeof(object[]);

        public static Func<object[], object> CreateCtor(ConstructorInfo constructorInfo)
        {
            var parametersParameter = Expression.Parameter(typeof(object[]), "parameters");
            var parameterExpressions = new List<Expression>();
            var paramInfos = constructorInfo.GetParameters();
            var length = paramInfos.Length;
            for (int i = 0; i < length; i++)
            {
                var parameterType = paramInfos[i].ParameterType;
                var valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
                var valueCast = Expression.Convert(valueObj, parameterType);
                parameterExpressions.Add(valueCast);
            }
            var instanceCreate = Expression.New(constructorInfo, parameterExpressions);
            var instanceCreateCast = Expression.Convert(instanceCreate, typeof(object));
            var lambda = Expression.Lambda<Func<object[], object>>(instanceCreateCast, parametersParameter);
            return lambda.Compile();
        }

        public static Func<object, object[], object> CreateMethod(MethodInfo methodinfo)
        {
            Func<object, object[], object> createdfunc;
            var instanceParameter = Expression.Parameter(OBJECT_TYPE, "instance");
            var parametersParameter = Expression.Parameter(OBJECTARRAY_TYPE, "parameters");
            var parameterExpressions = new List<Expression>();

            var paramInfos = methodinfo.GetParameters();
            var length = paramInfos.Length;
            for (int i = 0; i < length; i++)
            {
                var parameterType = paramInfos[i].ParameterType;
                BinaryExpression valueObj = Expression.ArrayIndex(parametersParameter, Expression.Constant(i));
                UnaryExpression valueCast = Expression.Convert(valueObj, parameterType);
                parameterExpressions.Add(valueCast);
            }
            var instanceCast = methodinfo.IsStatic ? null : Expression.Convert(instanceParameter, methodinfo.ReflectedType);
            var methodCall = Expression.Call(instanceCast, methodinfo, parameterExpressions);
            if (methodCall.Type == typeof(void))
            {
                var lambda = Expression.Lambda<Action<object, object[]>>(
                        methodCall, instanceParameter, parametersParameter);

                Action<object, object[]> execute = lambda.Compile();
                createdfunc = (instance, paras) =>
                {
                    execute(instance, paras);
                    return null;
                };
            }
            else
            {
                var castMethodCall = Expression.Convert(methodCall, typeof(object));
                var lambda = Expression.Lambda<Func<object, object[], object>>(
                    castMethodCall, instanceParameter, parametersParameter);
                createdfunc = lambda.Compile();
            }

            return createdfunc;
        }
    }
}
